﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.IO;

namespace ProtobufTools
{
    public class ProtobufCompiler
    {
        private Assembly assembly;

        private object objGenerator;

        string code = @"
using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using ProtoBuf;

namespace ProtobufTools
{
    public class Generator
    {
        public string Unpack<T>(string content, Encoding encode) where T : new()
        {
            string result = string.Empty;
            if (string.IsNullOrEmpty(content))
                return result;

            try
            {
                T protobufObject = new T();
                using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(content)))
                    protobufObject = Serializer.Deserialize<T>((Stream)memoryStream);

                XmlSerializer xmlSerializer = new XmlSerializer(protobufObject.GetType());
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
                    namespaces.Add(string.Empty, string.Empty);
                    xmlSerializer.Serialize((Stream)memoryStream, protobufObject, namespaces);
                    result = encode.GetString(memoryStream.GetBuffer());
                }
            }
            catch (Exception ex)
            {
                return ex.ToString();
            }

            return result;
        }

        public string Pack<T>(string content, Encoding encode) where T : new()
        {
            string result = string.Empty;
            if (string.IsNullOrEmpty(content))
                return result;

            try
            {
                T protobufObject = new T();
                T instance = default(T);
                XmlSerializer xmlSerializer = new XmlSerializer(protobufObject.GetType());
                using (MemoryStream memoryStream = new MemoryStream(encode.GetBytes(content)))
                {
                    instance = (T)xmlSerializer.Deserialize((Stream)memoryStream);
                }

                byte[] inArray = (byte[])null;
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    Serializer.Serialize<T>((Stream)memoryStream, instance);
                    inArray = memoryStream.ToArray();
                }
                result = Convert.ToBase64String(inArray);
            }
            catch (Exception ex)
            {
                return result + ex.ToString();
            }

            return result;
        }
    }
}";

        public ProtobufCompiler()
        {
            CreateAssembly();
        }

        private void CreateAssembly()
        {
            // 1.CSharpCodePrivoder
            CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();

            // 2.ICodeComplier
            ICodeCompiler objICodeCompiler = objCSharpCodePrivoder.CreateCompiler();

            // 3.CompilerParameters
            CompilerParameters objCompilerParameters = new CompilerParameters();
            objCompilerParameters.ReferencedAssemblies.Add("System.dll");
            objCompilerParameters.ReferencedAssemblies.Add("System.Xml.dll");
            objCompilerParameters.ReferencedAssemblies.Add("protobuf-net.dll");
            objCompilerParameters.GenerateExecutable = false;
            objCompilerParameters.GenerateInMemory = true;                  

            // 4.CompilerResults
            CompilerResults cr = objICodeCompiler.CompileAssemblyFromSource(objCompilerParameters, code.ToString());
            //CompilerResults cr = objICodeCompiler.CompileAssemblyFromFile(objCompilerParameters, "Generator.cs");

            if (!cr.Errors.HasErrors)
            {
                // 通过反射，调用HelloWorld的实例
                assembly = cr.CompiledAssembly;
                objGenerator = assembly.CreateInstance("ProtobufTools.Generator");
            }            
        }

        public string Unpack(string content, Type t)
        {
            if (assembly == null || objGenerator == null)
                return "There are some unknown errors, please contact the author!";

            MethodInfo objMI = objGenerator.GetType().GetMethod("Unpack");
            objMI = objMI.MakeGenericMethod(new Type[] { t });
            return (string)objMI.Invoke(objGenerator, new object[] { content, Encoding.UTF8 });
        }

        public string Pack(string content, Type t)
        {
            if (assembly == null || objGenerator == null)
                return "There are some unknown errors, please contact the author!";

            MethodInfo objMI = objGenerator.GetType().GetMethod("Pack");
            objMI = objMI.MakeGenericMethod(new Type[] { t });
            return (string)objMI.Invoke(objGenerator, new object[] { content, Encoding.UTF8 });
        }
        
    }
}
